package org.mozhu.mboot.core.data.support;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.mozhu.mboot.core.data.BaseRepository;
import org.mozhu.mboot.core.data.DynamicSpecifications;
import org.mozhu.mboot.core.data.RepositoryHelper;
import org.mozhu.mboot.core.data.Searchable;
import org.mozhu.mboot.core.data.entity.LogicDeleteable;
import org.mozhu.mboot.core.data.filter.SearchFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.*;

/**
 * 抽象基础Custom Repository 实现
 */
public class SimpleBaseRepository<M, ID extends Serializable> extends SimpleJpaRepository<M, ID> implements BaseRepository<M, ID> {

    public static final String LOGIC_DELETE_ALL_QUERY_STRING = "update %s x set x.deleted=true where x in (?1)";
    public static final String DELETE_ALL_QUERY_STRING = "delete from %s x where x in (?1)";
    public static final String FIND_QUERY_STRING = "from %s x where 1=1 ";
    public static final String COUNT_QUERY_STRING = "select count(x) from %s x where 1=1 ";

    private final EntityManager em;
    private final JpaEntityInformation<M, ID> entityInformation;

    private final RepositoryHelper repositoryHelper;

    private Class<M> entityClass;
    private String entityName;
    private String idName;

    public SimpleBaseRepository(JpaEntityInformation<M, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);

        this.entityInformation = entityInformation;
        this.entityClass = this.entityInformation.getJavaType();
        this.entityName = this.entityInformation.getEntityName();
        this.idName = this.entityInformation.getIdAttributeNames().iterator().next();
        this.em = entityManager;

        repositoryHelper = new RepositoryHelper(em, entityClass);
    }

    // ///////////////////////////////////////////////
    // //////覆盖默认spring data jpa的实现////////////
    // ///////////////////////////////////////////////

    /**
     * 根据主键删除相应实体
     *
     * @param id 主键
     */
    @Transactional
    @Override
    public void deleteById(final ID id) {
        M m = findById(id).get();
        delete(m);
    }

    /**
     * 删除实体
     *
     * @param m 实体
     */
    @Transactional
    @Override
    public void delete(final M m) {
        if (m == null) {
            return;
        }
        if (m instanceof LogicDeleteable) {
            ((LogicDeleteable) m).markDeleted();
            save(m);
        } else {
            super.delete(m);
        }
    }

    @Transactional
    @Override
    public void deleteInBatch(final Iterable<M> entities) {
        Iterator<M> iter = entities.iterator();
        if (entities == null || !iter.hasNext()) {
            return;
        }
        Set<M> models = Sets.newHashSet(iter);
        boolean logicDeletableEntity = LogicDeleteable.class.isAssignableFrom(this.entityClass);
        if (logicDeletableEntity) {
            String ql = String.format(LOGIC_DELETE_ALL_QUERY_STRING, entityName);
            repositoryHelper.batchUpdate(ql, models);
        } else {
            String ql = String.format(DELETE_ALL_QUERY_STRING, entityName);
            repositoryHelper.batchUpdate(ql, models);
        }
    }

    /**
     * 按照主键查询
     *
     * @param id 主键
     * @return 返回id对应的实体
     */
    @Transactional
    @Override
    public Optional<M> findById(ID id) {
        if (id == null) {
            return Optional.empty();
        }
        if (id instanceof Integer && ((Integer) id).intValue() == 0) {
            return Optional.empty();
        }
        if (id instanceof Long && ((Long) id).longValue() == 0L) {
            return Optional.empty();
        }
        return super.findById(id);
    }

    /**
     * 重写默认的 这样可以走一级/二级缓存
     *
     * @param id
     * @return
     */
    @Override
    public boolean existsById(ID id) {
        return findById(id) != null;
    }

    // ///////////////////////////////////////////////
    // /////////////////自定义实现////////////////////
    // ///////////////////////////////////////////////

    /**
     * 根据主键删除相应实体
     *
     * @param ids 实体
     */
    @Transactional
    @Override
    public void deleteInBatch(final ID[] ids) {
        if (ArrayUtils.isEmpty(ids)) {
            return;
        }
        List<M> models = new ArrayList<>();
        for (ID id : ids) {
            M model = null;
            try {
                model = entityClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("batch delete " + entityClass + " error", e);
            }
            try {
                BeanUtils.setProperty(model, idName, id);
            } catch (Exception e) {
                throw new RuntimeException("batch delete " + entityClass + " error, can not set id", e);
            }
            models.add(model);
        }
        deleteInBatch(models);
    }

    @Override
    public boolean support(String modelType) {
        return entityClass.getName().equals(modelType);
    }

    @Override
    public Page<M> findAll(List<SearchFilter> searchFilters, Pageable pageable) {
        return findAll(DynamicSpecifications.bySearchFilter(searchFilters, entityClass), pageable);
    }

    @Override
    public Page<M> findAll(SearchFilter searchFilter, Pageable pageable) {
        return findAll(DynamicSpecifications.bySearchFilter(searchFilter, entityClass), pageable);
    }

    @Override
    public List<M> findAll(List<SearchFilter> searchFilters) {
        return findAll(DynamicSpecifications.bySearchFilter(searchFilters, entityClass));
    }

    @Override
    public List<M> findAll(List<SearchFilter> searchFilters, Sort sort) {
        return findAll(DynamicSpecifications.bySearchFilter(searchFilters, entityClass), sort);
    }

    @Override
    public List<M> findAll(SearchFilter searchFilter, Sort sort) {
        return findAll(DynamicSpecifications.bySearchFilter(searchFilter, entityClass), sort);
    }

    @Override
    public Page<M> findAll(final Searchable searchable) {
        if (searchable.hasPageable()) {
            return findAll(Lists.newArrayList(searchable.getSearchFilters()), searchable.getPage());
        } else {
            List<M> list = findAll(Lists.newArrayList(searchable.getSearchFilters()), searchable.getSort());
            return new PageImpl<M>(list);
        }
    }

    @Override
    public long count(final Searchable searchable) {
        return count(DynamicSpecifications.bySearchFilter(searchable.getSearchFilters(), entityClass));
    }

    @Override
    public <X> List<X> findAll(String ql, Object... params) {
        return repositoryHelper.findAll(ql, params);
    }

    @Override
    public <X> Page<X> findAll(Pageable pageable, String ql, Object... params) {
        long count = repositoryHelper.count(prepareCountHql(ql), params);
        List<X> all = repositoryHelper.findAll(ql, pageable, params);
        return new PageImpl<X>(all, pageable, count);
    }

    private String prepareCountHql(String orgHql) {
        String fromHql = orgHql;
        // select子句与order by子句会影响count查询,进行简单的排除.
        fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
        fromHql = StringUtils.substringBefore(fromHql, "order by");

        String countHql = "select count(*) " + fromHql;
        return countHql;
    }

    @Override
    public <X> List<X> findAll(Sort sort, String ql, Object... params) {
        return repositoryHelper.findAll(ql, sort, params);
    }

    @Override
    public <X> X findOne(String ql, Object... params) {
        return repositoryHelper.findOne(ql, params);
    }

    @Override
    public long count(String ql, Object... params) {
        return repositoryHelper.count(ql, params);
    }

    @Override
    public int batchUpdate(String ql, Object... params) {
        return repositoryHelper.batchUpdate(ql, params);
    }

}
